using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace OrigPacman
{
    //This class holds the properties that are individual to the ghosts
    public class Ghost : Character
    {
        public enum State { RUN, ATTACK, DEAD };
        public enum Color { RED = 0, ORANGE = 1, GREEN = 2, BLUE = 3, WHITE = 4 };

        #region Variables
        protected State currentState;
        protected Color currentColor;
        protected Color superColor;
        protected Direction currentDirection;
        protected Texture2D texture;
        protected Rectangle container;
        protected Vector2 position;
        protected int lives;
        protected bool alive;
        protected int speed;
        protected int nextWayPoint;
        protected int prevWayPoint;
        protected int size;
        protected Game1 theGame;
        protected Vector2 ghostStartingPosition;
        #endregion

        #region constants
        public const int startSpeed = 65;
        public const int startNextWayPoint = 44;
        public const int startPrevWayPoint = 45;
        public const int ghostSize = 32;
        public const int defaultRandomNext = 99;
        public const int chanceOfRandomMoving = 50;
        public const float vectorMin = -0.45f;
        public const float vectorMax = 0.45f;
        public const float vectorBelowMin = -0.5f;
        public const float vectorOverMax = 0.5f;
        public const int moveRange = 90;
        public const int randomMin = 0;
        public const int randomMax = 3;
        public const float vectorOffsetZero = 0.0f;
        public const float vectorOffsetPosOne = 1.0f;
        public const float vectorOffsetNegOne = -1.0f;
        #endregion

        #region Properties
        public Texture2D Texture
        {
            get { return texture; }
            set
            {
                texture = value;
                container.Width = texture.Width;
                container.Height = texture.Height;
            }
        }
        public Vector2 Position
        {
            get { return position; }
            set
            {
                position = value;
                container.X = (int)position.X;
                container.Y = (int)position.Y;
            }
        }
        public State CurrentState
        {
            get { return currentState; }
            set { currentState = value; }
        }
        public Color CurrentColor
        {
            get { return currentColor; }
            set { currentColor = value; }
        }
        public Direction CurrentDirection
        {
            get { return currentDirection; }
            set { currentDirection = value; }
        }
        public int Speed
        {
            get { return speed; }
            set { speed = value; }
        }
        public int NextWayPoint
        {
            get { return nextWayPoint; }
            set { nextWayPoint = value; }
        }
        public int PreviousWayPoint
        {
            get { return prevWayPoint; }
            set { prevWayPoint = value; }
        }
        public Rectangle Bounds
        {
            get { return container; }
            set
            {
                container = value;
                position = new Vector2(container.X, container.Y);
            }
        }
        public int Size
        {
            get { return size; }
            set
            {
                size = value;
                container.Width = size;
                container.Height = size;
            }
        }
        public int Lives
        {
            get { return Lives; }
            set
            {
                lives = value;
                if (lives <= 0)
                {
                    alive = false;
                }
            }
        }
        public bool Alive
        {
            get { return alive; }
            set { alive = value; }
        }
        #endregion

        public Ghost(Color ghostColor, Vector2 start, Game1 currentGame)
        {
            alive = true;
            currentColor = ghostColor;
            position = start;
            this.ghostStartingPosition = start;
            currentState = State.ATTACK;
            currentDirection = Direction.SOUTH;
            speed = startSpeed;
            nextWayPoint = startNextWayPoint;
            prevWayPoint = startPrevWayPoint;
            container = new Rectangle((int)position.X, (int)position.Y, 0, 0);
            Size = ghostSize;
            this.theGame = currentGame;
        }

        #region Ghost Methods

        /// <summary>
        /// update the movement and status of the a ghost
        /// </summary>
        /// <param name="elapsed">elapsed time</param>
        /// <param name="pacman">pacman</param>
        /// <param name="rand">random from game</param>
        public void UpdateGhost(float elapsed, Pacman pacman, Random rand)
        {
            if (pacman.Super)
            {
                CurrentState = Ghost.State.RUN;
            }
            else if (Alive)
            {
                CurrentState = Ghost.State.ATTACK;
            }
            //If not at waypoint just update position and move to next ghost
            if (CurrentDirection == GetDirectionToWaypoint(Position, NextWayPoint) && CurrentDirection != Direction.STOP)
            {
                NormalUpdate(elapsed);
            }
            else
            {
                Vector2 length = (theGame.Waypoints[NextWayPoint].Position - Position);
                float distance = length.Length();
                int value;
                value = rand.Next(defaultRandomNext);

                // 50/50 chance of continuing or randomly moving
                if (value < chanceOfRandomMoving)
                {
                    Move(theGame.Waypoints[NextWayPoint], elapsed);
                }
                else
                {
                    RandomMove(theGame.Waypoints[NextWayPoint], elapsed, rand);
                }
            }
        }

        /// <summary>
        /// draw a ghost
        /// </summary>
        /// <param name="sprite">sprite batch to draw</param>
        public void RenderGhost(ref SpriteBatch sprite)
        {
            if (Alive)
            {
                if (theGame.Player.Super)
                {
                    /// flash the ghosts if the super time is almost up
                    if (theGame.SuperAlmostOver && !theGame.FlashGhost)
                    {
                        sprite.Draw(theGame.GhostTextures[CurrentColor], Bounds, Microsoft.Xna.Framework.Graphics.Color.White);
                    }
                    else
                    {
                        sprite.Draw(theGame.GhostTextures[Color.WHITE], Bounds, Microsoft.Xna.Framework.Graphics.Color.White);
                    }
                }
                else
                {
                    sprite.Draw(theGame.GhostTextures[CurrentColor], Bounds, Microsoft.Xna.Framework.Graphics.Color.White);
                }
            }
        }

        /// <summary>
        /// set the way point for a ghost
        /// </summary>
        /// <param name="list">waypoint list from game call</param>
        public void SetWaypoint(List<Waypoint> list)
        {
            Position = list[PreviousWayPoint].Position;
        }

        /// <summary>
        /// get the direction to get to a waypoint
        /// </summary>
        /// <param name="pos">position</param>
        /// <param name="nextWayIndex">next waypoint</param>
        /// <returns>direction</returns>
        private Direction GetDirectionToWaypoint(Vector2 pos, int nextWayIndex)
        {
            if (nextWayIndex > theGame.Waypoints.Count - 1)
                return Direction.STOP;
            Vector2 vec = theGame.Waypoints[nextWayIndex].Position - pos;
            vec.Normalize();
            if (vec.X < vectorMax && vec.X > vectorMin)
            {
                vec.X = vectorOffsetZero;
            }
            if (vec.X < vectorBelowMin)
            {
                vec.X = vectorOffsetNegOne;
                vec.Y = vectorOffsetZero;
            }
            if (vec.X > vectorOverMax)
            {
                vec.X = vectorOffsetPosOne;
                vec.Y = vectorOffsetZero;
            }
            if (vec.Y < vectorMax && vec.Y > vectorMin)
            {
                vec.Y = vectorOffsetZero;
            }
            if (vec.Y > vectorOverMax)
            {
                vec.Y = vectorOffsetPosOne;
                vec.X = vectorOffsetZero;
            }
            if (vec.Y < vectorBelowMin)
            {
                vec.Y = vectorOffsetNegOne;
                vec.X = vectorOffsetZero;
            }
            if (vec.X == vectorOffsetPosOne)
                return Direction.EAST;
            else if (vec.X == vectorOffsetNegOne)
                return Direction.WEST;
            else if (vec.Y == vectorOffsetPosOne)
                return Direction.SOUTH;
            else if (vec.Y == vectorOffsetNegOne)
                return Direction.NORTH;
            return Direction.STOP;
        }

        /// <summary>
        /// reverse the direction of the ghost
        /// </summary>
        /// <param name="dir">direction going</param>
        /// <returns>direction to go</returns>
        public Direction ReverseDirection(Direction dir)
        {
            if (dir == Direction.EAST)
                return Direction.WEST;
            if (dir == Direction.NORTH)
                return Direction.SOUTH;
            if (dir == Direction.SOUTH)
                return Direction.NORTH;
            if (dir == Direction.WEST)
                return Direction.EAST;

            return Direction.STOP;
        }

        /// <summary>
        /// get direction relative between ghost and pacman
        /// </summary>
        /// <param name="ghost">ghost</param>
        /// <param name="pac">pacman</param>
        /// <returns>direction</returns>
        public Direction GetRelativeDirection(Vector2 ghost, Vector2 pac)
        {
            //Calculate the greatest distance between pacman and ghost
            float Ydifference = Math.Abs(pac.Y - ghost.Y);
            float Xdifference = Math.Abs(pac.X - ghost.X);
            if (pac.Y > ghost.Y && Ydifference > Xdifference)
                return Direction.SOUTH;
            if (pac.Y < ghost.Y && Ydifference > Xdifference)
                return Direction.NORTH;
            if (pac.X > ghost.X && Xdifference > Ydifference)
                return Direction.EAST;
            if (pac.X < ghost.X && Xdifference > Ydifference)
                return Direction.WEST;
            return Direction.WEST;
        }

        /// <summary>
        /// get the next direction for the ghost
        /// </summary>
        /// <param name="dir">direction going</param>
        /// <returns>direction to go</returns>
        private Direction GetNextDirection(Direction dir)
        {
            if (dir == Direction.WEST)
                return Direction.NORTH;
            if (dir == Direction.NORTH)
                return Direction.EAST;
            if (dir == Direction.EAST)
                return Direction.SOUTH;
            if (dir == Direction.SOUTH)
                return Direction.WEST;
            if (dir == Direction.STOP)
                return Direction.EAST;

            return Direction.STOP;
        }

        /// <summary>
        /// try the next direction
        /// </summary>
        /// <param name="dir">next direction</param>
        /// <returns>boolean for if okay</returns>
        private bool TryNextDirection(Direction dir)
        {
            switch (dir)
            {
                case Direction.EAST:
                    if (theGame.Waypoints[NextWayPoint].East < theGame.Waypoints.Count - 1)
                        return true;
                    break;
                case Direction.NORTH:
                    if (theGame.Waypoints[NextWayPoint].North < theGame.Waypoints.Count - 1)
                        return true;
                    break;
                case Direction.SOUTH:
                    if (theGame.Waypoints[NextWayPoint].South < theGame.Waypoints.Count - 1)
                        return true;
                    break;
                case Direction.WEST:
                    if (theGame.Waypoints[NextWayPoint].West < theGame.Waypoints.Count - 1)
                        return true;
                    break;
            }
            return false;
        }

        /// <summary>
        /// set the new points for ghost
        /// </summary>
        private void SetNewPoints()
        {
            if (NextWayPoint > theGame.Waypoints.Count - 1 || NextWayPoint < 0)
                return;
            int prev;
            switch (CurrentDirection)
            {
                case Direction.EAST:
                    prev = NextWayPoint;
                    NextWayPoint = theGame.Waypoints[NextWayPoint].East;
                    PreviousWayPoint = prev;
                    break;
                case Direction.NORTH:
                    prev = NextWayPoint;
                    NextWayPoint = theGame.Waypoints[NextWayPoint].North;
                    PreviousWayPoint = prev;
                    break;
                case Direction.SOUTH:
                    prev = NextWayPoint;
                    NextWayPoint = theGame.Waypoints[NextWayPoint].South;
                    PreviousWayPoint = prev;
                    break;
                case Direction.WEST:
                    prev = NextWayPoint;
                    NextWayPoint = theGame.Waypoints[NextWayPoint].West;
                    PreviousWayPoint = prev;
                    break;
            }
        }

        /// <summary>
        /// update the ghost normally
        /// </summary>
        /// <param name="elapsed">elapsed time</param>
        public void NormalUpdate(float elapsed)
        {
            //Update Direction
            if (CurrentDirection == Direction.EAST)
            {
                Position = new Vector2(Position.X + (Speed * elapsed), Position.Y);
            }
            if (CurrentDirection == Direction.NORTH)
            {
                Position = new Vector2(Position.X, Position.Y - (Speed * elapsed));
            }
            if (CurrentDirection == Direction.SOUTH)
            {
                Position = new Vector2(Position.X, Position.Y + (Speed * elapsed));
            }
            if (CurrentDirection == Direction.WEST)
            {
                Position = new Vector2(Position.X - (Speed * elapsed), Position.Y);
            }
        }

        /// <summary>
        /// move the ghost
        /// </summary>
        /// <param name="wp">waypoint</param>
        /// <param name="elapsed">elapsed time</param>
        public void Move(Waypoint wp, float elapsed)
        {
            Vector2 length = (wp.Position - Position);
            float distance = length.Length();

            switch (CurrentDirection)
            {
                case Direction.EAST:
                    if (wp.East < theGame.Waypoints.Count - 1)
                    {
                        NextWayPoint = wp.East;
                        PreviousWayPoint = NextWayPoint;
                        Position = new Vector2(wp.Position.X + distance + (Speed * elapsed), wp.Position.Y);
                    }
                    break;
                case Direction.WEST:
                    if (wp.West < theGame.Waypoints.Count - 1)
                    {
                        NextWayPoint = wp.West;
                        PreviousWayPoint = NextWayPoint;
                        Position = new Vector2(wp.Position.X - (distance + (Speed * elapsed)), wp.Position.Y);
                    }
                    break;
                case Direction.NORTH:
                    if (wp.North < theGame.Waypoints.Count - 1)
                    {
                        NextWayPoint = wp.North;
                        PreviousWayPoint = NextWayPoint;
                        Position = new Vector2(wp.Position.X, wp.Position.Y - (distance + (Speed * elapsed)));
                    }
                    break;
                case Direction.SOUTH:
                    if (wp.South < theGame.Waypoints.Count - 1)
                    {
                        NextWayPoint = wp.South;
                        PreviousWayPoint = NextWayPoint;
                        Position = new Vector2(wp.Position.X, wp.Position.Y + distance + (Speed * elapsed));
                    }
                    break;
            }
        }

        /// <summary>
        /// move the ghost in a random fashion
        /// </summary>
        /// <param name="wp">waypoint</param>
        /// <param name="elapsed">elapsed time</param>
        /// <param name="rand">random from game</param>
        private void RandomMove(Waypoint wp, float elapsed, Random rand)
        {
            int value = rand.Next(randomMin, randomMax);
            switch (value)
            {
                case 0://Move WEST
                    if (wp.West < moveRange)
                    {
                        int tmp = NextWayPoint;
                        NextWayPoint = wp.West;
                        PreviousWayPoint = tmp;
                        CurrentDirection = Direction.WEST;
                    }
                    else //Pick another direction
                    {
                        CurrentDirection = GetNextDirection(CurrentDirection);
                        while (!TryNextDirection(CurrentDirection))
                        {
                            CurrentDirection = GetNextDirection(CurrentDirection);
                        }
                        SetNewPoints();
                    }
                    break;
                case 1: //EAST
                    if (wp.East < moveRange)
                    {
                        int tmp = NextWayPoint;
                        NextWayPoint = wp.East;
                        PreviousWayPoint = tmp;
                        CurrentDirection = Direction.EAST;
                    }
                    else
                    {
                        CurrentDirection = GetNextDirection(CurrentDirection);
                        while (!TryNextDirection(CurrentDirection))
                        {
                            CurrentDirection = GetNextDirection(CurrentDirection);
                        }
                        SetNewPoints();
                    }
                    break;
                case 2:  //South
                    if (wp.South < moveRange)
                    {
                        int tmp = NextWayPoint;
                        NextWayPoint = wp.South;
                        PreviousWayPoint = tmp;
                        CurrentDirection = Direction.SOUTH;
                    }
                    else
                    {
                        CurrentDirection = GetNextDirection(CurrentDirection);
                        while (!TryNextDirection(CurrentDirection))
                        {
                            CurrentDirection = GetNextDirection(CurrentDirection);
                        }
                        SetNewPoints();
                    }
                    break;
                case 3:
                    if (wp.North < moveRange)
                    {
                        int tmp = NextWayPoint;
                        NextWayPoint = wp.North;
                        PreviousWayPoint = tmp;
                        CurrentDirection = Direction.NORTH;
                    }
                    else
                    {
                        CurrentDirection = GetNextDirection(CurrentDirection);
                        while (!TryNextDirection(CurrentDirection))
                        {
                            CurrentDirection = GetNextDirection(CurrentDirection);
                        }
                        SetNewPoints();
                    }
                    break;
            }
        }

        public void Reset(List<Waypoint> waypoints)
        {
            nextWayPoint = startNextWayPoint;
            prevWayPoint = startPrevWayPoint;
            Position = waypoints[startPrevWayPoint].Position;
            currentDirection = Direction.SOUTH;
        }
        #endregion
    }
}